/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.core;

import cn.seaboot.commons.lang.Symbol;
import cn.seaboot.commons.lang.Warning;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * 字符串工具
 * <p>
 * 包含各种业务中会用到的一些工具函数，与其它开源框架的 StringUtils 完全是两个东西。
 *
 * @author Mr.css 2018年7月13日 上午11:16:09
 * @version Mr.css 2020年8月17日 下午16:50:09 删除无用的函数
 */
public class StringUtils {
    private StringUtils() {
    }

    /**
     * 是否为空
     *
     * @param str -
     * @return true/false
     */
    public static boolean isEmpty(String str) {
        return str == null || str.isEmpty();
    }

    /**
     * 是否为空
     *
     * @param str -
     * @return true/false
     */
    public static boolean isNotEmpty(String str) {
        return (str != null && !str.isEmpty());
    }

    /**
     * 是否为空白串（检查所有空白字符）
     *
     * @param str a string
     * @return boolean
     */
    public static boolean containsWhitespace(@NotNull String str) {
        for (int i = 0, len = str.length(); i < len; i++) {
            if (Character.isWhitespace(str.charAt(i))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 减少字符串中的空白符
     * <p>
     * 将字符串中重复的换行符、空白符合并成一个，E.G.：将多行 SQL 转为单行的。
     *
     * @param str a string
     * @return boolean
     */
    public static String reduceWhitespace(@NotNull String str) {
        StringBuilder ret = new StringBuilder();
        char ch;
        boolean hit = true;
        int len = str.length();
        for (int i = 0; i < len; i++) {
            ch = str.charAt(i);
            if (Character.isWhitespace(ch)) {
                if (!hit) {
                    ret.append(' ');
                    hit = true;
                }
            } else {
                ret.append(ch);
                hit = false;
            }
        }
        len = ret.length() - 1;
        if (ret.charAt(len) == ' ') {
            ret.deleteCharAt(len);
        }
        return ret.toString();
    }

    /**
     * 是否为空白串（检查所有空白字符）
     *
     * @param str a string
     * @return boolean
     */
    private static boolean containsText(CharSequence str) {
        int strLen = str.length();
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(str.charAt(i))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否为空白串（检查所有空白字符）
     *
     * @param str a string
     * @return boolean
     */
    public static boolean notContainsWhitespace(String str) {
        return !containsWhitespace(str);
    }


    /**
     * Check whether the given {@code CharSequence} contains actual <em>text</em>.
     * <p>More specifically, this method returns {@code true} if the
     * {@code CharSequence} is not {@code null}, its length is greater than
     * 0, and it contains at least one non-whitespace character.
     * <p><pre class="code">
     * StringUtils.hasText(null) = false
     * StringUtils.hasText("") = false
     * StringUtils.hasText(" ") = false
     * StringUtils.hasText("12345") = true
     * StringUtils.hasText(" 12345 ") = true
     * </pre>
     *
     * @param str the {@code CharSequence} to check (may be {@code null})
     * @return {@code true} if the {@code CharSequence} is not {@code null},
     * its length is greater than 0, and it does not contain whitespace only
     * @see #hasText(String)
     * @see Character#isWhitespace
     */
    public static boolean hasText(@Nullable CharSequence str) {
        return str != null && str.length() > 0 && containsText(str);
    }

    /**
     * Check whether the given {@code String} contains actual <em>text</em>.
     * <p>More specifically, this method returns {@code true} if the
     * {@code String} is not {@code null}, its length is greater than 0,
     * and it contains at least one non-whitespace character.
     *
     * @param str the {@code String} to check (may be {@code null})
     * @return {@code true} if the {@code String} is not {@code null}, its
     * length is greater than 0, and it does not contain whitespace only
     * @see #hasText(CharSequence)
     * @see Character#isWhitespace
     */
    public static boolean hasText(@Nullable String str) {
        return str != null && !str.isEmpty() && containsText(str);
    }

    /**
     * 字符串分隔
     * <p>
     * 与 {@link String#split(String)} 区别：分割之后，会保留空值；按字符分隔；
     * <p>
     * E.G.：",,a,,"，分割后的结果是 [null,null,a,null,null]
     *
     * @param str A String
     * @param c   split
     * @return array not null
     */
    public static List<String> splitAsList(@NotNull String str, char c) {
        char[] chs = str.toCharArray();
        int idx = 0;
        char ch;
        List<String> list = new ArrayList<>();
        for (int i = 0; i < chs.length; i++) {
            ch = chs[i];
            if (ch == c) {
                list.add(str.substring(idx, i));
                idx = i + 1;
            }
        }
        list.add(str.substring(idx));
        return list;
    }

    /**
     * 字符串分割之后，按类型转换成对应的数组
     * <p>
     * 与 {@link String#split(String)} 区别：分割之后，会保留空值；按字符分隔；
     *
     * @param str A String
     * @param c   split
     * @return array not null
     */
    @SuppressWarnings(Warning.UNCHECKED)
    public static <T> T[] splitAs(@NotNull String str, char c, Class<T> clazz) {
        List<String> list = splitAsList(str, c);
        T[] arr = (T[]) ArrayUtils.newArray(clazz, list.size());
        for (int i = 0; i < list.size(); i++) {
            arr[i] = Converter.convert(list.get(i), clazz);
        }
        return arr;
    }

    /**
     * 字符串分隔
     * <p>
     * 与 {@link String#split(String)} 区别：分割之后，会保留空值；按字符分隔；
     * <p>
     * E.G.：",,a,,"，分割后的结果是 [null,null,a,null,null]
     *
     * @param str A String
     * @param c   split
     * @return array not null
     */
    public static String[] split(@NotNull String str, char c) {
        return splitAsList(str, c).toArray(new String[0]);
    }

    /**
     * 按照逗号分隔
     *
     * @param str 字符串
     * @return 数组
     */
    public static String[] split(String str) {
        return split(str, Symbol.COMMA);
    }

    /**
     * 数组拼接
     *
     * @param objs 数组
     * @return a not null String
     */
    public static <T> String join(T[] objs) {
        return join(objs, Symbol.COMMA);
    }

    /**
     * 数组拼接
     *
     * @param objs 数组
     * @return a not null String
     */
    public static <T> String join(List<T> objs) {
        return join(objs, Symbol.COMMA);
    }

    /**
     * 数组拼接
     *
     * @param objs   数组
     * @param symbol 分隔符
     * @return a not null String
     */
    public static <T> String join(T[] objs, char symbol) {
        if (objs == null || objs.length == 0) {
            return "";
        } else if (objs.length == 1) {
            return Converter.toString(objs[0]);
        } else {
            StringBuilder sb = new StringBuilder().append(Converter.toString(objs[0]));
            for (int i = 1; i < objs.length; i++) {
                sb.append(symbol).append(Converter.toString(objs[i]));
            }
            return sb.toString();
        }
    }

    /**
     * 数组拼接
     *
     * @param objs   数组
     * @param symbol 分隔符
     * @return a not null String
     */
    public static <T> String join(List<T> objs, char symbol) {
        if (objs == null || objs.isEmpty()) {
            return "";
        } else if (objs.size() == 1) {
            return Converter.toString(objs.get(0));
        } else {
            StringBuilder sb = new StringBuilder().append(Converter.toString(objs.get(0)));
            for (int i = 1; i < objs.size(); i++) {
                sb.append(symbol).append(Converter.toString(objs.get(i)));
            }
            return sb.toString();
        }
    }

    /**
     * Unicode字符串转中文
     *
     * @param unicode Unicode字符串
     * @return 中文
     */
    public static String unicodeToChn(String unicode) {
        String[] strings = unicode.split("\\\\u");
        StringBuilder returnStr = new StringBuilder();
        for (int i = 1; i < strings.length; i++) {
            returnStr.append((char) Integer.valueOf(strings[i], 16).intValue());
        }
        return returnStr.toString();
    }

    /**
     * 中文转成unicode
     *
     * @param chn 中文
     * @return Unicode字符串
     */
    public static String chnToUnicode(String chn) {
        char[] chars = chn.toCharArray();
        StringBuilder returnStr = new StringBuilder();
        for (char ch : chars) {
            returnStr.append("\\u").append(Integer.toString(ch, 16));
        }
        return returnStr.toString();
    }

    /**
     * 判断一个字符串包含多少个子字符串，用于段落文本替换等功能
     *
     * @param str 字符串
     * @param sub 子字符串
     * @return 数量
     */
    public static int getSubStringCount(String str, String sub) {
        int count = 0, index = 0;
        while ((index = str.indexOf(sub, index)) != -1) {
            index = index + sub.length();
            count++;
        }
        return count;
    }

    /**
     * 字符串转Unicode，防止出现中文乱码
     *
     * @param input a String
     * @return a Unicode String
     */
    public static String toUnicode(String input) {
        StringBuilder builder = new StringBuilder();
        char[] chars = input.toCharArray();
        for (char ch : chars) {
            if (ch < 256) {
                builder.append(ch);
            } else {
                builder.append("\\u").append(Integer.toHexString(ch & 0xffff));
            }
        }
        return builder.toString();
    }

    /**
     * 帕斯卡命名转小驼峰式命名
     * E.G.:  StringUtils => stringUtils
     *
     * @param str a String
     * @return a String
     */
    public static String pascalToCamel(String str) {
        return str.substring(0, 1).toLowerCase().concat(str.substring(1));
    }

    /**
     * 下划线式命名转帕斯卡命名
     * E.G.:  string_utils => StringUtils
     *
     * @param str 字符串
     * @return 帕斯卡命名字符串
     */
    public static String underlineToPascal(String str) {
        return underlineToPascalBuilder(str).toString();
    }

    /**
     * 下划线式命名转驼峰式命名
     * E.G.:  string_utils => stringUtils
     *
     * @param str 字符串
     * @return 驼峰式命名字符串
     */
    public static String underlineToCamel(String str) {
        StringBuilder sb = underlineToPascalBuilder(str);
        sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
        return sb.toString();
    }

    /**
     * 下划线命名转帕斯卡命名
     * E.G.:  string_utils => StringUtils
     *
     * @param str 字符串
     * @return 字符串
     */
    private static StringBuilder underlineToPascalBuilder(String str) {
        StringBuilder result = new StringBuilder();
        boolean upper = false;
        if (str != null && !str.isEmpty()) {
            if (str.charAt(0) == '_') {
                upper = true;
            } else {
                result.append(Character.toUpperCase(str.charAt(0)));
            }
            for (int i = 1; i < str.length(); i++) {
                char c = str.charAt(i);
                if (c == '_') {
                    upper = true;
                } else {
                    if (upper) {
                        result.append(Character.toUpperCase(c));
                        upper = false;
                    } else {
                        result.append(Character.toLowerCase(c));
                    }
                }
            }
        }
        return result;
    }


    /**
     * 小驼峰式命名转帕斯卡命名
     * E.G.:  stringUtils => StringUtils
     *
     * @param str a String
     * @return a String
     */
    public static String camelToPascal(String str) {
        return str.substring(0, 1).toUpperCase(Locale.ROOT).concat(str.substring(1));
    }

    /**
     * 驼峰式命名转小写下划线命名
     *
     * @param str -
     * @return -
     */
    public static String camelToUnderline(String str) {
        int len = str.length();
        StringBuilder sb = new StringBuilder(len);
        char node = str.charAt(0);
        if (Character.isUpperCase(node)) {
            node = Character.toLowerCase(node);
        }
        sb.append(node);
        for (int i = 1; i < len; i++) {
            node = str.charAt(i);
            if (Character.isUpperCase(node)) {
                sb.append('_').append(Character.toLowerCase(node));
            } else {
                sb.append(node);
            }
        }
        return sb.toString();
    }

    /**
     * 截断开头部分，保留尾部 n 个字符
     *
     * @param str 字符串
     * @param len 截取长度（最终返回值的最大长度）
     * @return 截取之后的字符串
     */
    public static String truncateHead(@NotNull String str, int len) {
        return str.length() > len ? str.substring(str.length() - len) : str;
    }

    /**
     * 截断尾部，保留开头 n 个字符
     *
     * @param str 字符串
     * @param len 截取长度（最终返回值的最大长度）
     * @return 截取之后的字符串
     */
    public static String truncateTail(@NotNull String str, int len) {
        return str.length() > len ? str.substring(0, len) : str;
    }

    /**
     * 省略字符串结尾
     * <p>
     * 保留字符串开头的 n 个字符。
     * <p>
     * 如果发生截断，增加省略号
     *
     * @param str 字符串
     * @param len 截取长度（最终返回值的最大长度）
     * @return 截取之后的字符串
     */
    public static String ellipsisTail(@NotNull String str, int len) {
        return ellipsisTail(str, len, Symbol.APOSTROPHE_EN);
    }

    /**
     * 省略字符串结尾
     * <p>
     * 如果发生截断，增加省略号
     *
     * @param str    字符串
     * @param len    截取长度（最终返回值的最大长度）
     * @param suffix 省略号
     * @return 截取之后的字符串
     */
    public static String ellipsisTail(@NotNull String str, int len, String suffix) {
        int n = len - str.length() - suffix.length();
        if (str.length() > n) {
            return str.substring(0, n) + suffix;
        } else {
            return str;
        }
    }

    /**
     * 省略字符串开头
     * <p>
     * 如果发生截断，结尾拼接上英文省略号
     *
     * @param str 字符串
     * @param len 截取长度（最终返回值的最大长度）
     * @return 截取之后的字符串
     */
    public static String ellipsisHead(@NotNull String str, int len) {
        return ellipsisHead(str, len, Symbol.APOSTROPHE_EN);
    }

    /**
     * 省略字符串开头
     * <p>
     * 如果发生截断，增加省略号
     *
     * @param str    字符串
     * @param len    截取长度（最终返回值的最大长度）
     * @param prefix 省略号
     * @return 截取之后的字符串
     */
    public static String ellipsisHead(@NotNull String str, int len, String prefix) {
        int n = len - str.length() - prefix.length();
        if (str.length() > n) {
            return prefix + str.substring(n);
        } else {
            return str;
        }
    }
}
